#include "common.h"
#include "memory/cache.h"
#include "memory/tlb.h"
#include "memory/memory.h"
#include "burst.h"
#include "cpu/reg.h"

//记录当前使用段寄存器编号，这样就不用修改很多的调用swaddr_read和write的地方了
extern uint8_t current_sreg;  //该变量定义在reg.h

uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);


/* Memory accessing interfaces */

uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	uint32_t offset = addr & ((1 << cache_b) - 1);
	uint32_t block = cache1_read(addr);
	uint8_t temp[32];
	memset(temp, 0, sizeof(temp));
	
	if (offset + len >= cache_B) {//处理跨越多个缓存行存储的数据
		uint32_t block2 = cache1_read(addr + len);
		memcpy(temp, cache1[block].cache_line + offset, cache_B - offset);
		memcpy(temp + cache_B - offset, cache1[block2].cache_line, len - (cache_B - offset));
	} 
	else {
		memcpy(temp, cache1[block].cache_line + offset, len);
	}
	uint32_t res = 0;
	memcpy(&res, temp, len);
	return res & (~0u >> ((4 - len) << 3));
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	cache1_write(addr, len, data);
}

uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	//传进来的是虚拟地址经过段翻译后的线性地址
	uint32_t loffset = addr & 0xfff;//取出低12位为offset
	if (loffset + len - 1 > 0xfff){
		size_t len1 = 0xfff - loffset + 1;//该页能取的字节数
		size_t len2 = len - len1;//跨页取的字节数
		//递归调用
		uint32_t addr_len1 = lnaddr_read(addr,len1);
		uint32_t addr_len2 = lnaddr_read(addr + len1,len2);
		//大端法存储-->addr_len2在前而addr_len1在后
		uint32_t val = (addr_len2 << (len1 << 3)) | addr_len1;
		return val;
	}
	hwaddr_t hwaddr = page_translate(addr);
	return hwaddr_read(hwaddr,len);
	// return hwaddr_read(addr, len);
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	uint32_t now_offset = addr & 0xfff;
	if (now_offset + len - 1 > 0xfff){
		size_t len1 = 0xfff - now_offset + 1;
    	size_t len2 = len - len1;
		//((1 << (len1 << 3)) - 1))是要写入对应位的掩码
    	lnaddr_write(addr, len1, data & ((1 << (len1 << 3)) - 1));
    	lnaddr_write(addr + len1, len2, data >> (len1 << 3));
	}
	else {
		hwaddr_t hwaddr = page_translate(addr);
		hwaddr_write(hwaddr, len, data);
	}
	// hwaddr_write(addr, len, data);
}

uint32_t swaddr_read(swaddr_t addr, size_t len) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr = seg_translate(addr,len);
	return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr = seg_translate(addr,len);
	lnaddr_write(lnaddr, len, data);
}




lnaddr_t seg_translate(swaddr_t addr,size_t len){
	if (cpu.cr0.pe == 0) //实模式
		return addr; 
	//保护模式，传进来的addr是段内偏移量offset
	//printf("offset:0x%x\n", addr);
	//printf("eip:0x%x\n",cpu.eip);
	return cpu.sreg[current_sreg].base + addr;
}


hwaddr_t page_translate(lnaddr_t addr){
if (cpu.cr0.pe == 1 && cpu.cr0.pg == 1) {
		//分别取出线性地址的三个部分:10位dir，10位page，10位offset
		uint32_t dir = addr >> 22;//页表在页目录(一级页表)中的索引
    	uint32_t page = (addr >> 12) & 0x3ff;//页在页表中的索引
		uint32_t offset = addr & 0xfff;//字节在页内偏移量

		//读页表项(TLE)的缓存TLB
		//uint32_t tlb_tag = addr >> 12; 
    	int i = read_tlb(addr);
		//有缓存，直接取
    	if (i != -1) { 
      		return (tlb[i].page_num << 12) + offset;
    	}

		//没缓存，一步步由二级页表翻译

		//由页目录(一级页表)得到页表地址
		//一级页表基址
    	uint32_t dir_start = cpu.cr3.pdbr;
		//得到二级页表在页目录中地址，这里<<2是因为每个页表项32位(4字节)
    	uint32_t dir_pos = (dir_start << 12) + (dir << 2);
		//由上面地址取出页描述符
    	PTE level1_PTE;
    	level1_PTE.val = hwaddr_read(dir_pos, 4);
    	Assert(level1_PTE.p == 1, "Dir cannot be used!");
    	
		//由二级页表得到页地址
		//二级页表基地址
		uint32_t page_start = level1_PTE.addr;
		//得到页在二级页表中地址，这里<<2是因为每个页表项32位(4字节)
    	uint32_t page_pos = (page_start << 12) + (page << 2);
    	//由上面地址取出页描述符
		PTE level2_PTE;
    	level2_PTE.val =  hwaddr_read(page_pos, 4);
    	Assert(level2_PTE.p == 1, "Page cannot be used!");
    	
		//由页描述符得到物理地址
		page_start = level2_PTE.addr;
    	hwaddr_t hwaddr = (page_start << 12) + offset;
    	
		//缓存到TLB
		write_tlb(addr, hwaddr);
    	
		return hwaddr;
  	} 
	return addr;
}
